﻿using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity;

/// <summary>
/// Creates a new instance of a persistence store for the specified user type.
/// </summary>
/// <remarks>
/// Constructs a new instance of <see cref="UserStore{TUser}"/>.
/// </remarks>
/// <param name="context">The <see cref="DbContext"/>.</param>
/// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
public class UserStore(IdentityDbContext context, DefaultIdentityErrorDescriber describer) : UserStore<User, Role, UserClaim, UserRole, UserLogin, UserToken, RoleClaim, Group, UserGroup, Level, IdentityDbContext, string>(context, describer) { }

/// <summary>
/// Represents a new instance of a persistence store for the specified user and role types.
/// </summary>
/// <typeparam name="TUser">The type representing a user.</typeparam>
/// <typeparam name="TRole">The type representing a role.</typeparam>
/// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
/// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
public class UserStore<TUser, TRole, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TGroup, TUserGroup, TLevel, TContext, TKey> :
    UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>,
    IIdentityStore<TUser, TKey>,
    IUserGroupStore<TUser, TKey>,
    ILevelStore<TUser, TKey>
    where TKey : IEquatable<TKey>, IConvertible
    where TContext : DbContext
    where TRole : Role<TKey>, new()
    where TUser : User<TKey>, new()
    where TGroup : Group<TKey>, new()
    where TUserGroup : UserGroup<TKey>, new()
    where TUserClaim : UserClaim<TKey>, new()
    where TUserRole : UserRole<TKey>, new()
    where TUserLogin : UserLogin<TKey>, new()
    where TUserToken : UserToken<TKey>, new()
    where TRoleClaim : RoleClaim<TKey>, new()
    where TLevel : Level<TKey>, new()
{
    /// <summary>
    /// Constructs a new instance of <see cref="UserStore{TUser, TRole, TContext, TKey}"/>.
    /// </summary>
    /// <param name="context">The <see cref="DbContext"/>.</param>
    /// <param name="describer">The <see cref="DefaultIdentityErrorDescriber"/>.</param>
    public UserStore(TContext context, DefaultIdentityErrorDescriber describer) : base(context, describer)
    {
        _context = context;
        _errorDescriber = describer;
        _groups = _context.Set<TGroup>();
        _userGroups = _context.Set<TUserGroup>();
        _levels = _context.Set<TLevel>();
    }

    protected readonly DbContext _context;
    protected readonly DefaultIdentityErrorDescriber _errorDescriber;
    protected readonly DbSet<TGroup> _groups;
    protected readonly DbSet<TUserGroup> _userGroups;
    protected readonly DbSet<TLevel> _levels;

    #region implement from IIdentityStore
    /// <summary>
    /// 使用 id 获取当前用户
    /// </summary>
    /// <param name="id"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<TUser> FindByIdAsync(TKey id, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (id.Equals(default))
        {
            throw new ArgumentNullException(nameof(id));
        }

        return await _context.Set<TUser>().FindAsync(new object[] { id }, cancellationToken) ?? throw new ArgumentNullException(nameof(id), $"the user of id {id} not exist!");
    }

    /// <summary>
    /// 修改名字
    /// </summary>
    /// <param name="user"></param>
    /// <param name="name"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetNameAsync(TUser user, string name, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user is null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Name = name;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改昵称
    /// </summary>
    /// <param name="user"></param>
    /// <param name="alias"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetAliasAsync(TUser user, string alias, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user is null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Alias = alias;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改头像
    /// </summary>
    /// <param name="user"></param>
    /// <param name="image"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetImageAsync(TUser user, string image, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user is null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Image = image;
        return Task.CompletedTask;
    }
    /// <summary>
    /// 修改用户类型
    /// </summary>
    /// <param name="user"></param>
    /// <param name="authorizeType"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual Task SetAuthorizeTypeAsync(TUser user, AuthorizeType authorizeType, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user is null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        user.Type = authorizeType;
        return Task.CompletedTask;
    }

    /// <summary>
    /// 设置用户级别
    /// </summary>
    /// <param name="user"></param>
    /// <param name="level"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task SetLevelAsync(TUser user, string level, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();
        if (user is null)
        {
            throw new ArgumentNullException(nameof(user));
        }

        var existLevel = await _levels.FirstOrDefaultAsync(x => x.Name == level, cancellationToken) ?? throw new ArgumentNullException(nameof(level));
        user.LevelId = existLevel.Id;
    }
    #endregion

    #region implement from IUserGroupStore
    /// <summary>
    /// 用户添加到组织(组)
    /// </summary>
    /// <param name="user"></param>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> AddToGroupAsync(TUser user, string group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (string.IsNullOrWhiteSpace(group))
        {
            throw new ArgumentNullException(nameof(group));
        }

        var _group = await _groups.FirstOrDefaultAsync(x => x.Name == group, cancellationToken);
        if (_group is null)
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(group));
        }

        if (user.Name is not null && await _userGroups.AnyAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(_group.Id), cancellationToken))
        {
            return IdentityResult.Failed(_errorDescriber.UserAlreadyInGroup(user.Name, group));
        }

        try
        {
            await _userGroups.AddAsync(new TUserGroup { UserId = user.Id, GroupId = _group.Id }, cancellationToken);
            await SaveChanges(cancellationToken);
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 用户添加到组织(组)
    /// </summary>
    /// <param name="user"></param>
    /// <param name="groups"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> AddToGroupsAsync(TUser user, IEnumerable<string> groups, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (groups.IsNullOrEmpty())
        {
            throw new ArgumentNullException(nameof(groups));
        }

        var all = await _groups.Where(x => groups.Contains(x.Name)).ToListAsync(cancellationToken);
        if (all is null)
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(groups.ToString<string>()));
        }

        var notExists = groups.Except(all.Select(x => x.Name ?? string.Empty));
        if (notExists is not null && notExists.Any())
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(notExists.ToString<string>()));
        }

        var exists = await _userGroups.Where(x => x.UserId.Equals(user.Id)).Select(x => x.GroupId).Distinct().ToListAsync(cancellationToken);
        if (exists is not null && exists.Count != 0)
        {
            var adds = all.Select(x => x.Id).Except(exists);
            if (adds.Any())
            {
                try
                {
                    var userGroups = adds.Select(x => new TUserGroup { UserId = user.Id, GroupId = x });
                    await _userGroups.AddRangeAsync(userGroups, cancellationToken);
                    await SaveChanges(cancellationToken);
                }
                catch (DbUpdateConcurrencyException)
                {
                    return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
                }
            }
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 从组织中移除用户
    /// </summary>
    /// <param name="user"></param>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> RemoveFromGroupAsync(TUser user, string group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (string.IsNullOrWhiteSpace(group))
        {
            throw new ArgumentNullException(nameof(group));
        }

        var _group = await _groups.FirstOrDefaultAsync(x => x.Name == group, cancellationToken);
        if (_group is null)
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(group));
        }

        var userGroup = await _userGroups.FirstOrDefaultAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(_group.Id), cancellationToken);
        if (userGroup is null && user.Name is not null)
        {
            return IdentityResult.Failed(_errorDescriber.UserNotInGroup(user.Name, group));
        }

        try
        {
            if (userGroup is not null)
            {
                _userGroups.Remove(userGroup);
                await SaveChanges(cancellationToken);
            }
        }
        catch (DbUpdateConcurrencyException)
        {
            return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 从组织中移除用户
    /// </summary>
    /// <param name="user"></param>
    /// <param name="groups"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IdentityResult> RemoveFromGroupsAsync(TUser user, IEnumerable<string> groups, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (groups.IsNullOrEmpty())
        {
            throw new ArgumentNullException(nameof(groups));
        }

        var all = await _groups.Where(x => groups.Contains(x.Name)).ToListAsync(cancellationToken);
        if (all is null)
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(groups.ToString<string>()));
        }

        var notExists = groups.Except(all.Select(x => x.Name ?? string.Empty));
        if (notExists.Any())
        {
            return IdentityResult.Failed(_errorDescriber.InvalidGroupName(notExists.ToString<string>()));
        }

        var exists = await _userGroups.Where(x => x.UserId.Equals(user.Id)).Select(x => x.GroupId).Distinct().ToListAsync(cancellationToken: cancellationToken);
        if (exists is not null && exists.Count != 0)
        {
            var removes = exists.Intersect(all.Select(x => x.Id));
            if (removes.Any())
            {
                try
                {
                    var userGroups = removes.Select(x => new TUserGroup { UserId = user.Id, GroupId = x });
                    _userGroups.RemoveRange(userGroups);
                    await SaveChanges(cancellationToken);
                }
                catch (DbUpdateConcurrencyException)
                {
                    return IdentityResult.Failed(_errorDescriber.ConcurrencyFailure());
                }
            }
        }

        return IdentityResult.Success;
    }
    /// <summary>
    /// 获取组织中所有用户
    /// </summary>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IList<TUser>> GetGroupUsersAsync(string group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (group.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(group));
        }

        var query = from _userGroup in _userGroups
                    join _group in _groups on _userGroup.GroupId equals _group.Id
                    join _user in Users on _userGroup.UserId equals _user.Id
                    where _group.Name == @group
                    select _user;
        return await query.ToListAsync(cancellationToken);
    }
    /// <summary>
    /// 获取用户加入的所有组织名称
    /// </summary>
    /// <param name="user"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<IList<string>> GetUserGroupsAsync(TUser user, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        var query = from _userGroup in _userGroups
                    join _group in _groups on _userGroup.GroupId equals _group.Id
                    where _userGroup.UserId != null && _userGroup.UserId.Equals(user.Id)
                    select _group.Name;
        return await query.ToListAsync(cancellationToken);
    }
    /// <summary>
    /// 用户是否在组织中
    /// </summary>
    /// <param name="user"></param>
    /// <param name="group"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public virtual async Task<bool> IsInGroupAsync(TUser user, string group, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        ThrowIfDisposed();

        if (user is null || user.Id.Equals(default))
        {
            throw new ArgumentNullException(nameof(user));
        }

        if (group.IsNullOrWhiteSpace())
        {
            throw new ArgumentNullException(nameof(group));
        }

        var query = from _userGroup in _userGroups
                    join _group in _groups on _userGroup.GroupId equals _group.Id
                    where _userGroup.UserId != null && _userGroup.UserId.Equals(user.Id) && _group.Name == @group
                    select _group.Id;
        return await query.AnyAsync(cancellationToken);
    }
    #endregion
}